<template>
  <div
    ref="gridWrapperRef"
    class="grid-designer-wrapper"
    @mousemove="handleWrapperMouseMove"
    @mouseup="handleWrapperMouseUp"
    @mouseleave="handleWrapperMouseLeave"
  >
    <table
      ref="gridTableRef"
      class="grid-table"
      @mousedown="handleTableMouseDownGeneral"
    >
      <colgroup>
        <col style="width: 50px" />
        <!-- Row header column -->
        <col
          v-for="(col, index) in grid.cols"
          :key="`col-def-${index}`"
          :style="{ width: `${Math.max(50, col.width || 0)}px` }"
        />
      </colgroup>
      <thead>
        <tr>
          <th class="header-cell corner-cell"></th>
          <!-- Top-left corner cell -->
          <th
            v-for="(col, colIdx) in grid.cols"
            :key="`header-col-${colIdx}`"
            class="header-cell col-header"
            :class="{
              'resizing-target':
                potentialResizeTarget?.type === 'col' &&
                potentialResizeTarget?.index === colIdx,
            }"
            @mousemove="checkColResizeHover(colIdx, $event)"
            @mousedown.stop.prevent="startColResize(colIdx, $event)"
          >
            {{ getColumnLabel(colIdx) }}
            {{ colIdx + 1 }}
          </th>
        </tr>
      </thead>
      <tbody>
        <tr
          v-for="(row, rowIndex) in grid.rows"
          :key="`row-${rowIndex}`"
          :style="{ height: `${row.height}px` }"
        >
          <td
            class="header-cell row-header"
            :class="{
              'resizing-target':
                potentialResizeTarget?.type === 'row' &&
                potentialResizeTarget?.index === rowIndex,
            }"
            @mousemove="checkRowResizeHover(rowIndex, $event)"
            @mousedown.stop.prevent="startRowResize(rowIndex, $event)"
          >
            {{ rowIndex + 1 }}
          </td>
          <template
            v-for="cell in getRenderableCellsInRow(rowIndex)"
            :key="cell.key"
          >
            <td
              :data-row="cell.rowIndex"
              :data-col="cell.colIndex"
              :class="getCellClass(cell)"
              :style="getCellStyle(cell)"
              :colspan="cell.colSpan"
              :rowspan="cell.rowSpan"
              @mousedown.stop="handleCellMouseDown(cell, $event)"
              @mousemove.stop="handleCellMouseMoveWhenDown(cell, $event)"
              @mouseenter="handleCellMouseEnter(cell)"
            >
              <div class="cell-content-wrapper">
                <span
                  v-if="cell.content?.type === 'text' && (cell.content as TextContent).valueType === 'metaModel' && cell.dataMetaModelField"
                  :style="{ color: (cell.content as TextContent).metaModelPlaceholderColor }"
                >
                  {{ getMetaModelFieldPlaceholder(cell.dataMetaModelField) }}
                </span>
                <span
                  v-else-if="cell.content?.type === 'text' && (cell.content as TextContent).valueType === 'predefined' && (cell.content as TextContent).predefinedType === 'currentDate'"
                >
                  {{
                    formatDate(
                      new Date(),
                      (cell.content as TextContent).dateFormat
                    )
                  }}
                  (日期)
                </span>
                <span v-else-if="cell.content?.type === 'text'">
                  {{ (cell.content as TextContent).staticText || '' }}
                </span>
                <span v-else-if="cell.content?.type === 'input'">
                  [输入框:
                  {{ (cell.content as InputContent).prop || '未绑定' }}]
                </span>
                <span v-else-if="cell.content?.type === 'image'">[图片]</span>
                <span v-else>{{ cell.rowIndex }},{{ cell.colIndex }}</span>
              </div>
            </td>
          </template>
        </tr>
      </tbody>
    </table>
    <div
      v-if="resizingState && resizingPreviewLine.visible"
      class="resize-preview-line"
      :style="resizingPreviewLine.style"
    ></div>
  </div>
</template>

<script setup lang="ts">
import {
  ref,
  computed,
  PropType,
  StyleValue,
  onBeforeUnmount,
  reactive,
} from 'vue';
import { GridDefinition } from '@/models/grid.model';
import { CellDefinition } from '@/models/cell.model';
import { TextContent, InputContent } from '@/models/content.model';
import { DataMetaModel } from '@/models/meta-model.model';

const props = defineProps({
  grid: { type: Object as PropType<GridDefinition>, required: true },
  selectedCells: {
    type: Array as PropType<CellDefinition[]>,
    default: () => [],
  },
  dataMetaModel: {
    type: Object as PropType<DataMetaModel | undefined>,
    default: undefined,
  },
});

const emit = defineEmits<{
  (e: 'update:selectedCells', cells: CellDefinition[]): void;
  (e: 'cell-clicked', cell: CellDefinition): void;
  (e: 'selection-changed', cells: CellDefinition[]): void;
  (e: 'resize-row', payload: { index: number; height: number }): void;
  (e: 'resize-col', payload: { index: number; width: number }): void;
}>();

const gridWrapperRef = ref<HTMLElement | null>(null);
const gridTableRef = ref<HTMLTableElement | null>(null);

interface ResizingState {
  type: 'row' | 'col';
  index: number;
  startX: number; // Mouse X at start of resize
  startY: number; // Mouse Y at start of resize
  startSize: number; // Original width/height
  initialScrollLeft: number; // Scroll position at start
  initialScrollTop: number; // Scroll position at start
}
const resizingState = ref<ResizingState | null>(null);
const RESIZE_SENSITIVITY = 5; // Pixels from edge

const resizingPreviewLine = reactive<{ visible: boolean; style: StyleValue }>({
  visible: false,
  style: {},
});

// For cursor change on hover over resizable edge
interface PotentialResizeTarget {
  type: 'row' | 'col';
  index: number;
}
const potentialResizeTarget = ref<PotentialResizeTarget | null>(null);

function checkColResizeHover(colIndex: number, event: MouseEvent): void {
  if (resizingState.value) {
    if (
      gridWrapperRef.value &&
      gridWrapperRef.value.style.cursor !== 'col-resize' &&
      gridWrapperRef.value.style.cursor !== 'row-resize'
    ) {
      gridWrapperRef.value.style.cursor = 'default';
    }
    potentialResizeTarget.value = null;
    return;
  }
  const th = event.currentTarget as HTMLTableHeaderCellElement;
  const rect = th.getBoundingClientRect();
  const isAtEdge = Math.abs(event.clientX - rect.right) < RESIZE_SENSITIVITY;

  if (gridWrapperRef.value) {
    gridWrapperRef.value.style.cursor = isAtEdge ? 'col-resize' : 'default';
  }
  potentialResizeTarget.value = isAtEdge
    ? { type: 'col', index: colIndex }
    : null;
}

function startColResize(colIndex: number, event: MouseEvent): void {
  const colDef = props.grid.cols[colIndex];
  if (colDef && gridWrapperRef.value) {
    resizingState.value = {
      type: 'col',
      index: colIndex,
      startX: event.clientX,
      startY: event.clientY,
      startSize: colDef.width,
      initialScrollLeft: gridWrapperRef.value.scrollLeft,
      initialScrollTop: gridWrapperRef.value.scrollTop,
    };
    document.addEventListener('mousemove', handleDocumentMouseMoveForResize);
    document.addEventListener('mouseup', handleDocumentMouseUpForResize);
    gridWrapperRef.value.style.cursor = 'col-resize';

    if (gridTableRef.value) {
      const tableRect = gridTableRef.value.getBoundingClientRect();
      const wrapperRect = gridWrapperRef.value.getBoundingClientRect();
      let currentLeft = 50; // Add width of row header column
      for (let i = 0; i <= colIndex; i++) {
        currentLeft += props.grid.cols[i].width;
      }

      resizingPreviewLine.style = {
        position: 'absolute',
        left: `${
          currentLeft -
          gridWrapperRef.value.scrollLeft +
          (gridTableRef.value.offsetLeft - gridWrapperRef.value.offsetLeft)
        }px`,
        top: `${tableRect.top - wrapperRect.top}px`,
        width: '2px',
        height: `${tableRect.height}px`,
        backgroundColor: '#007bff',
        zIndex: '100',
      };
      resizingPreviewLine.visible = true;
    }
  }
}

function checkRowResizeHover(rowIndex: number, event: MouseEvent): void {
  if (resizingState.value) {
    if (
      gridWrapperRef.value &&
      gridWrapperRef.value.style.cursor !== 'col-resize' &&
      gridWrapperRef.value.style.cursor !== 'row-resize'
    ) {
      gridWrapperRef.value.style.cursor = 'default';
    }
    potentialResizeTarget.value = null;
    return;
  }
  const td = event.currentTarget as HTMLTableCellElement;
  const rect = td.getBoundingClientRect();
  const isAtEdge = Math.abs(event.clientY - rect.bottom) < RESIZE_SENSITIVITY;

  if (gridWrapperRef.value) {
    gridWrapperRef.value.style.cursor = isAtEdge ? 'row-resize' : 'default';
  }
  potentialResizeTarget.value = isAtEdge
    ? { type: 'row', index: rowIndex }
    : null;
}

function startRowResize(rowIndex: number, event: MouseEvent): void {
  const rowDef = props.grid.rows[rowIndex];
  if (rowDef && gridWrapperRef.value) {
    resizingState.value = {
      type: 'row',
      index: rowIndex,
      startX: event.clientX,
      startY: event.clientY,
      startSize: rowDef.height,
      initialScrollLeft: gridWrapperRef.value.scrollLeft,
      initialScrollTop: gridWrapperRef.value.scrollTop,
    };
    document.addEventListener('mousemove', handleDocumentMouseMoveForResize);
    document.addEventListener('mouseup', handleDocumentMouseUpForResize);
    gridWrapperRef.value.style.cursor = 'row-resize';

    if (gridTableRef.value) {
      const tableRect = gridTableRef.value.getBoundingClientRect();
      const wrapperRect = gridWrapperRef.value.getBoundingClientRect();
      const thHeight =
        gridTableRef.value.querySelector('thead tr')?.getBoundingClientRect()
          .height || 0;
      let currentTop = thHeight;
      for (let i = 0; i <= rowIndex; i++) {
        currentTop += props.grid.rows[i].height;
      }

      resizingPreviewLine.style = {
        position: 'absolute',
        top: `${
          currentTop -
          gridWrapperRef.value.scrollTop +
          (gridTableRef.value.offsetTop - gridWrapperRef.value.offsetTop)
        }px`,
        left: `${tableRect.left - wrapperRect.left}px`,
        height: '2px',
        width: `${tableRect.width}px`,
        backgroundColor: '#007bff',
        zIndex: '100',
      };
      resizingPreviewLine.visible = true;
    }
  }
}

function handleDocumentMouseMoveForResize(event: MouseEvent): void {
  if (!resizingState.value || !gridWrapperRef.value || !gridTableRef.value)
    return;
  event.preventDefault();

  const { type, index, startX, startY, startSize } = resizingState.value;
  const tableOffsetLeft =
    gridTableRef.value.offsetLeft - gridWrapperRef.value.offsetLeft;
  const tableOffsetTop =
    gridTableRef.value.offsetTop - gridWrapperRef.value.offsetTop;

  if (type === 'col') {
    const newWidth = startSize + (event.clientX - startX);
    if (
      resizingPreviewLine.visible &&
      typeof resizingPreviewLine.style.left === 'string'
    ) {
      let currentLeft = 0;
      for (let i = 0; i < index; i++) {
        currentLeft += props.grid.cols[i].width;
      }
      currentLeft += Math.max(20, newWidth);
      currentLeft += 50; // Add width of row header column
      resizingPreviewLine.style.left = `${
        currentLeft - gridWrapperRef.value.scrollLeft + tableOffsetLeft
      }px`;
    }
  } else if (type === 'row') {
    const newHeight = startSize + (event.clientY - startY);
    if (
      resizingPreviewLine.visible &&
      typeof resizingPreviewLine.style.top === 'string'
    ) {
      const thHeight =
        gridTableRef.value.querySelector('thead tr')?.getBoundingClientRect()
          .height || 0;
      let currentTop = thHeight;
      for (let i = 0; i < index; i++) {
        currentTop += props.grid.rows[i].height;
      }
      currentTop += Math.max(10, newHeight);
      resizingPreviewLine.style.top = `${
        currentTop - gridWrapperRef.value.scrollTop + tableOffsetTop
      }px`;
    }
  }
}

function handleDocumentMouseUpForResize(event: MouseEvent): void {
  if (!resizingState.value) return;
  const { type, index, startX, startY, startSize } = resizingState.value;

  if (type === 'col') {
    const newWidth = startSize + (event.clientX - startX);
    emit('resize-col', { index, width: Math.max(20, newWidth) });
  } else if (type === 'row') {
    const newHeight = startSize + (event.clientY - startY);
    emit('resize-row', { index, height: Math.max(10, newHeight) });
  }

  resizingState.value = null;
  resizingPreviewLine.visible = false;
  potentialResizeTarget.value = null;
  document.removeEventListener('mousemove', handleDocumentMouseMoveForResize);
  document.removeEventListener('mouseup', handleDocumentMouseUpForResize);
  if (gridWrapperRef.value) gridWrapperRef.value.style.cursor = 'default';
}

const isSelectingWithMouseDown = ref<boolean>(false);
const selectionStartCell = ref<CellDefinition | null>(null);

function getCellFromDomElement(
  element: EventTarget | null
): CellDefinition | null {
  if (!element || !(element instanceof HTMLElement)) return null;
  const td = element.closest('td[data-row][data-col]');
  if (td instanceof HTMLTableCellElement && td.dataset.row && td.dataset.col) {
    return (
      props.grid.getCell(
        parseInt(td.dataset.row, 10),
        parseInt(td.dataset.col, 10)
      ) || null
    );
  }
  return null;
}

function handleCellMouseDown(cell: CellDefinition, event: MouseEvent): void {
  if (event.button !== 0) return;

  isSelectingWithMouseDown.value = true;
  // Use getCellFromDomElement to ensure we get the cell based on DOM attributes
  const cellFromDom = getCellFromDomElement(event.target);
  if (!cellFromDom) {
    // Fallback if for some reason we can't get the cell from DOM, though unlikely for a cell click
    selectionStartCell.value = cell;
    if (!(event.shiftKey || event.ctrlKey || event.metaKey)) {
      emit('update:selectedCells', [cell]);
    }
    emit('cell-clicked', cell);
    return;
  }

  selectionStartCell.value = cellFromDom;

  if (!(event.shiftKey || event.ctrlKey || event.metaKey)) {
    emit('update:selectedCells', [cellFromDom]);
  }
  // For single click, also notify property editor
  emit('cell-clicked', cellFromDom);
}

function handleCellMouseMoveWhenDown(
  cellUnderMouse: CellDefinition, // This cell object might have an incorrect colIndex
  event: MouseEvent
): void {
  if (
    !isSelectingWithMouseDown.value ||
    !selectionStartCell.value ||
    event.buttons !== 1
  ) {
    return;
  }

  // Get the cell under the mouse using the DOM element to ensure correct indices
  const cellUnderMouseFromDom = getCellFromDomElement(event.target);

  if (!cellUnderMouseFromDom) {
    return; // Should not happen if mouse is over a cell
  }

  const r1 = selectionStartCell.value.rowIndex;
  const c1 = selectionStartCell.value.colIndex;

  const r2 = cellUnderMouseFromDom.rowIndex; // Use rowIndex from the cell obtained from DOM
  const c2 = cellUnderMouseFromDom.colIndex; // Use colIndex from the cell obtained from DOM

  const minR = Math.min(r1, r2);
  const maxR = Math.max(r1, r2);
  const minC = Math.min(c1, c2);
  const maxC = Math.max(c1, c2);

  const newlySelected: CellDefinition[] = [];
  for (let r = minR; r <= maxR; r++) {
    for (let c = minC; c <= maxC; c++) {
      const currentCell = props.grid.getCell(r, c);
      if (currentCell) {
        // Select all cells in rect, even merged slaves for simplicity of selection logic
        newlySelected.push(currentCell);
      }
    }
  }
  emit('update:selectedCells', newlySelected);
}

function handleWrapperMouseUp(event: MouseEvent): void {
  if (event.button !== 0) return;
  if (isSelectingWithMouseDown.value) {
    isSelectingWithMouseDown.value = false;
    emit('selection-changed', props.selectedCells);
  }
  // Note: Resizing mouseup is handled by handleDocumentMouseUpForResize
}

function handleWrapperMouseLeave(event: MouseEvent): void {
  if (isSelectingWithMouseDown.value && event.buttons !== 1) {
    handleWrapperMouseUp(event); // If mouse was down and now isn't, treat as mouseup
  }
  // Reset resize cursor if mouse leaves wrapper while not actively resizing
  if (!resizingState.value && gridWrapperRef.value) {
    gridWrapperRef.value.style.cursor = 'default';
    potentialResizeTarget.value = null;
  }
}

function handleWrapperMouseMove(event: MouseEvent): void {
  // Continue selection if mouse moves fast over cells
  if (
    isSelectingWithMouseDown.value &&
    selectionStartCell.value &&
    event.buttons === 1
  ) {
    const cellUnderMouse = getCellFromDomElement(event.target);
    if (cellUnderMouse) {
      handleCellMouseMoveWhenDown(cellUnderMouse, event);
    }
  }
  // Reset non-active resize hover if not on a resizable edge
  if (
    !resizingState.value &&
    gridWrapperRef.value &&
    gridWrapperRef.value.style.cursor !== 'default' &&
    !potentialResizeTarget.value
  ) {
    // if cursor is resize but not on a target
    const target = event.target as HTMLElement;
    if (
      !target.classList.contains('col-header') &&
      !target.classList.contains('row-header')
    ) {
      gridWrapperRef.value.style.cursor = 'default';
    }
  }
}

// General mousedown on table, not specific to cell or header resizer
function handleTableMouseDownGeneral(event: MouseEvent): void {
  if (event.button !== 0) return;
  // If clicked on table background (not a cell or header), potentially clear selection
  if (
    event.target === gridTableRef.value ||
    event.target === gridWrapperRef.value
  ) {
    // emit('update:selectedCells', []);
    // emit('selection-changed', []);
    // emit('cell-clicked', null); // No cell is active for props
  }
}

const hoveredCellKey = ref<string | null>(null);
function handleCellMouseEnter(cell: CellDefinition): void {
  if (!isSelectingWithMouseDown.value) {
    hoveredCellKey.value = cell.key;
  }
}

onBeforeUnmount(() => {
  document.removeEventListener('mousemove', handleDocumentMouseMoveForResize);
  document.removeEventListener('mouseup', handleDocumentMouseUpForResize);
});

function getRenderableCellsInRow(rowIndex: number): CellDefinition[] {
  const rowCells: CellDefinition[] = [];
  for (let j = 0; j < props.grid.colCount; j++) {
    const cell = props.grid.getCell(rowIndex, j);
    if (cell && !cell.isMergedSlave) {
      rowCells.push(cell);
    }
  }
  return rowCells;
}

function getCellStyle(cell: CellDefinition): StyleValue {
  const style: StyleValue = { ...cell.style };
  if (cell.width) style.width = `${cell.width}px`;
  if (cell.height) style.height = `${cell.height}px`;
  return style;
}

function getCellClass(cell: CellDefinition): string[] {
  const classes = ['grid-cell'];
  if (props.selectedCells.some((sc) => sc.key === cell.key)) {
    classes.push('selected');
  }
  if (cell.colIndex === 0) {
    // Add class for the first data column (Column A)
    classes.push('first-data-col');
  }
  if (
    hoveredCellKey.value === cell.key &&
    !isSelectingWithMouseDown.value &&
    !props.selectedCells.some((sc) => sc.key === cell.key)
  ) {
    classes.push('hovered');
  }
  return classes;
}

function getColumnLabel(colIndex: number): string {
  let label = '';
  let num = colIndex + 1;
  while (num > 0) {
    const remainder = (num - 1) % 26;
    label = String.fromCharCode(65 + remainder) + label;
    num = Math.floor((num - 1) / 26);
  }
  return label;
}

function getMetaModelFieldPlaceholder(fieldKey?: string): string {
  if (!fieldKey || !props.dataMetaModel) return '字段占位';
  const field = props.dataMetaModel.fields.find((f) => f.field === fieldKey);
  return field ? `\${${field.name}}` : `\${${fieldKey}}`;
}

function formatDate(date: Date, formatStr?: string): string {
  if (!formatStr) formatStr = 'YYYY-MM-DD HH:mm:ss';
  const Y = date.getFullYear().toString();
  const M = (
    date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1
  ).toString();
  const D = (
    date.getDate() < 10 ? '0' + date.getDate() : date.getDate()
  ).toString();
  const H = (
    date.getHours() < 10 ? '0' + date.getHours() : date.getHours()
  ).toString();
  const Min = (
    date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()
  ).toString();
  const S = (
    date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds()
  ).toString();
  return formatStr
    .replace('YYYY', Y)
    .replace('MM', M)
    .replace('DD', D)
    .replace('HH', H)
    .replace('mm', Min)
    .replace('ss', S);
}

const grid = computed(() => props.grid);
</script>

<style lang="scss" scoped>
.grid-designer-wrapper {
  width: 100%;
  overflow: auto;
  border: 1px solid #ccc;
  user-select: none;
  position: relative;
  background-color: #fdfdfd; // Light background for the wrapper itself
}

.grid-table {
  border-collapse: collapse;
  table-layout: fixed;
  min-width: max-content; /* 或者 width: max-content; */
  background-color: #fff; // Table background

  th.header-cell,
  td.header-cell {
    background-color: #f0f2f5; // A bit darker for headers
    border: 1px solid #d9d9d9; // Slightly darker borders for headers
    // padding: 6px 8px; // Adjusted padding
    font-weight: 500; // Slightly bolder
    text-align: center;
    position: sticky;
    white-space: nowrap;
    font-size: 13px;
  }

  thead th.header-cell {
    top: 0;
    z-index: 20;
  }

  tbody td.header-cell {
    position: sticky; /* 明确为 tbody 的行头也设置 sticky */
    left: 0;
    z-index: 10;
    background-color: #f0f2f5; /* 确保有背景色 */
    min-width: 10px;
  }
  /* 针对左上角的单元格 (假设它在 thead > tr > th.corner-cell) */
  thead th.corner-cell {
    /* 或者直接用 th.corner-cell 如果它只可能在 thead */
    position: sticky;
    top: 0;
    left: 0;
    z-index: 30; /* 最高，因为它覆盖行头和列头的交界处 */
    background-color: #e8eaed; /* 可以是不同的背景色 */
  }

  .grid-cell {
    border: 1px solid #e8e8e8;
    // padding: 8px;
    position: relative; /* 注意：如果 grid-cell 内部有绝对定位元素，这个会创建新的定位上下文 */
    min-width: 10px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    vertical-align: top;
    cursor: cell;
    font-size: 14px;

    &.selected {
      background-color: #e6f7ff !important;
      box-shadow: inset -1px 0 0 2px #1890ff, inset 1px 0 0 2px #1890ff,
        inset 0 -1px 0 2px #1890ff, inset 0 1px 0 2px #1890ff;
    }
    &.hovered {
      background-color: #fafafa;
    }
  }
  .cell-content-wrapper {
    // min-height: 20px;
    display: block;
  }
}
.resize-preview-line {
  pointer-events: none;
}
</style>
